package v0

import (
	"fmt"
	"io"
	"sync"
	"sync/atomic"
)

type ObjectRef struct {
	lk   sync.RWMutex
	refN atomic.Int32

	owner   *RefObjectList
	connstr string
	inner   interface{}
}

func (this *ObjectRef) GetObject() interface{} {
	return this.inner
}

// 确保没有被释放
func (this *ObjectRef) CheckGetOrCreate() (interface{}, error) {
	this.lk.RLock()
	inner := this.inner
	this.lk.RUnlock()
	if inner != nil {
		return inner, nil
	}

	this.lk.Lock()
	defer this.lk.Unlock()
	if this.inner != nil {
		return this.inner, nil
	}
	obj, err := this.owner.createObjectFunc(this.connstr)
	if err != nil {
		return nil, err
	}
	this.inner = obj
	return obj, nil
}

func (this *ObjectRef) RefN() int32 {
	return this.refN.Load()
}

func (this *ObjectRef) incRef() int32 {
	return this.refN.Add(1)
}

func (this *ObjectRef) decRef() int32 {
	return this.refN.Add(-1)
}

func (this *ObjectRef) ReleaseRef() error {
	return this.owner.releaseRef(this)
}

var (
	ErrorClosed      = fmt.Errorf("The object has been closed.")
	defRefObjectList = NewRefObjectList()
)

// 引用对象
type RefObjectList struct {
	lk               sync.RWMutex
	lst              map[string]*ObjectRef
	createObjectFunc func(conf string) (obj interface{}, err error)
	closeFn          func(obj any) (err error)
}

func NewRefObjectList() *RefObjectList {
	return &RefObjectList{lst: make(map[string]*ObjectRef), createObjectFunc: nullCreateFunc}
}

func nullCreateFunc(conf string) (obj interface{}, err error) {
	return nil, fmt.Errorf("CreateObjectFunc is null")
}

func (this *RefObjectList) Acquire(conf string) (handle any, object any, err error) {
	objRef := this.CheckGetOrCreate(conf)
	object, err = objRef.CheckGetOrCreate()
	if err != nil {
		objRef.ReleaseRef()
		return nil, nil, err
	}
	handle = objRef
	return
}

func (this *RefObjectList) closeInner(obj any) error {
	fn := this.closeFn
	if fn != nil {
		return fn(obj)
	}
	if intf, ok := obj.(io.Closer); ok {
		return intf.Close()
	}
	return nil
}

func (this *RefObjectList) Release(handle any) error {
	if handle == nil {
		return fmt.Errorf("handle is nil")
	}

	itm, ok := handle.(*ObjectRef)
	if !ok {
		return fmt.Errorf("invalid handle")
	}
	return itm.ReleaseRef()
}

func (this *RefObjectList) AcquireRefCount(handle any) (int, error) {
	if handle == nil {
		return 0, fmt.Errorf("handle is nil")
	}

	if ref, ok := handle.(*ObjectRef); ok {
		return int(ref.RefN()), nil
	}

	return 0, fmt.Errorf("invalid handle")
}

func (this *RefObjectList) SetNewObjectFunc(fn func(conf string) (any, error)) {
	this.lk.Lock()
	defer this.lk.Unlock()
	if fn == nil {
		fn = nullCreateFunc
	}
	this.createObjectFunc = fn
}

func (this *RefObjectList) SetCloseObjectFunc(fn func(obj any) error) {
	this.lk.Lock()
	defer this.lk.Unlock()
	this.closeFn = fn
}

func (this *RefObjectList) SetCreateObjectFunc(fn func(conf string) (obj interface{}, err error)) {
	this.lk.Lock()
	defer this.lk.Unlock()
	if fn == nil {
		fn = nullCreateFunc
	}
	this.createObjectFunc = fn
}

func (this *RefObjectList) tryGet(conf string) *ObjectRef {
	this.lk.RLock()
	defer this.lk.RUnlock()
	itm := this.lst[conf]
	if itm != nil {
		itm.incRef()
		return itm
	}
	return nil
}

func (this *RefObjectList) CheckGetOrCreate(conf string) *ObjectRef {
	itm := this.tryGet(conf)
	if itm != nil {
		return itm
	}
	this.lk.Lock()
	itm = this.lst[conf]
	if itm == nil {
		itm = &ObjectRef{
			connstr: conf,
			owner:   this,
		}
		this.lst[conf] = itm
	}
	itm.incRef()
	this.lk.Unlock()
	return itm
}

func (this *RefObjectList) releaseRef(itm *ObjectRef) error {
	n := itm.decRef()
	if n == 0 {
		delflag := 0
		this.lk.Lock()
		if itm.RefN() == 0 {
			delete(this.lst, itm.connstr)
			itm.owner = nil
			delflag = 1
		}
		this.lk.Unlock()

		if delflag == 1 {
			obj := itm.inner
			itm.inner = nil
			return this.closeInner(obj)
		}
	} else if n < 0 {
		return ErrorClosed
	}
	return nil
}

func SetCreateObjectFunc(fn func(conf string) (obj interface{}, err error)) {
	defRefObjectList.SetCreateObjectFunc(fn)
}

func CheckGetOrCreate(conf string) *ObjectRef {
	return defRefObjectList.CheckGetOrCreate(conf)
}
